# -*- coding: utf-8 -*-
'''
by:lelewow@qq.com

'''
from pytz import utc
from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor
from flask import Flask,request,jsonify,Response;
from apscheduler.events import EVENT_JOB_ERROR, EVENT_JOB_MISSED, EVENT_JOB_EXECUTED
import os;
import time
from config import Config;
import platform;
from apscheduler.triggers.cron import  CronTrigger
from apscheduler.triggers.interval import  IntervalTrigger
import logging;
from sqlalchemy import create_engine, text

import subprocess
import io


url = Config().MYSQLURL;
'''
初始化Flask
'''
app = Flask(__name__)
app.config.from_object(Config())

'''
初始化db_engine
'''
db_engine = create_engine(Config.MYSQLURL, max_overflow=5)
db_conn = db_engine.connect()
db_conn.execute(Config().INITCRONTAB);


'''
初始化日志文件以及运行文件
'''
if os.path.exists(Config().RUNPATH) ==False:
    os.makedirs(Config().RUNPATH)

if os.path.exists(Config().LOGPATH) ==False:
    os.makedirs(Config().LOGPATH)


'''
设置存储以及执行进程相关
'''
jobstores = {
    'default': SQLAlchemyJobStore(url=url,tablename=Config().MYSQLTABLE)
}

executors = {
    'default': ThreadPoolExecutor(20),
    'processpool': ProcessPoolExecutor(5)
}

job_defaults = {
    'coalesce': False,
    'max_instances': 3
}

def runPopen(cmd):
    
    res = {};
    proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1)
    proc.wait()
    stdout = io.TextIOWrapper(proc.stdout, encoding='utf-8')
    stderr = io.TextIOWrapper(proc.stderr, encoding='utf-8')
    str_stdout = str(stdout.read())
    str_stderr = str(stderr.read())
    
    res['message'] = str_stdout;
    res['error'] = str_stderr;
    
    return res;
    

#运行定时任务
def runJob(cmd,logName,process_only = 0):
    
    #日志设置
    logName = Config().LOGPATH + logName  + '.log';
    logging.basicConfig(level=logging.INFO,filename=logName, filemode='a',format=Config().LOGFORMAT)
    logger = logging.getLogger(__name__)
    
    
    #window下常规运行 
    if platform.system()=='Windows':
       
        std = runPopen(cmd);
        str_stdout = std['message'];
        str_stderr = std['error'];
        
        if str_stderr:
            logger.error(str_stderr);
        else:
            logger.info(str_stdout)
        
            
    else:
        '''
                   死循环运行，检查进程不存在执行
        '''
        if process_only ==1:
            cmdList = cmd.strip(' ').split(' ');
            cmdBin = cmdList[0];
            cmdFile = cmdList[1];
            cmdGrep = "ps -ef|grep %s\ %s|grep -v grep |awk '{print $2,$9}'"%(cmdBin,cmdFile);
            
            std = runPopen(cmdGrep);
            str_stdout = std['message'];
            str_stderr = std['error'];
            
            if str_stderr:
                logger.error(str_stderr);
            else:
                for i in str_stdout.strip('\n').split('\n'):
                    if i.find(cmdFile)<0:
                        res = runPopen(cmdGrep);
                        if res['error']:
                            logger.error(res['error']);
                        else:
                            logger.info(res['message'])
         
        #Linux下常规运行 
        else:
            res = runPopen(cmd);
            if res['error']:
                logger.error(res['error']);
            else:
                logger.info(res['message'])
        
    
#立即执行任务
@app.route('/runCmd', methods=['GET'])
def runCmd():
    
    cmd_type = request.args.get('cmd_type');
    cmd = request.args.get('cmd');
    
    if cmd_type and cmd:
        res = runPopen(cmd);
        if res['error']:
            return "执行出错:" +res['error'].replace('\n','<br/>');
        else:
            return "执行成功:" +res['message'].replace('\n','<br/>');
        

#任务列表
@app.route('/jobList', methods=['GET', 'POST'])
def jobList():
    
    
    sql = " SELECT * ,FROM_UNIXTIME(add_time,'%Y-%m-%d %H:%i:%s') add_time_format ,FROM_UNIXTIME(update_time,'%Y-%m-%d %H:%i:%s') update_time_format  FROM job_cron WHERE stats IN (0,1) ";
    
    rows = db_conn.execute(text(sql));
    res = [{key: value for (key, value) in row.items()} for row in rows]
    
    for i in res:
        i['logName'] =   i['job_name'] + '.log';
        
    
    return jsonify(res)
   

#新增定时任务API
@app.route('/addJob', methods=['POST'])
def addJob():
    
    job_name = request.form['job_name'];
    job_desc = request.form['job_desc'];
    cmd_type = request.form['cmd_type'];
    process_only = request.form['process_only'];
    cmd = request.form['cmd'];
    job_time = request.form['job_time'];
    add_time = int(time.time());
    update_time = int(time.time());
    
    jobList = checkJobTime(job_time);
    if jobList ==False:
        return printJson("执行周期格式错误");
    
    trig = jobList['trig'];
    
    field = "`job_name`,`job_desc`,`cmd_type`,`cmd`,`trig`,`job_time`,`add_time`,`update_time`,`process_only`";
    values = "'%s','%s','%s','%s','%s','%s','%s','%s','%s' "%(job_name,job_desc,cmd_type,cmd,trig,job_time,add_time,update_time,process_only);
    
    sql = "replace into job_cron(%s) values(%s)"%(field,values);
    
    res = db_conn.execute(sql);
    
    
    
    
    if res:
        runCmd = cmd_type + " " + Config().RUNPATH  + cmd;
        if trig =="interval":
            
            scheduler.add_job(func= __name__ + ":runJob",id=job_name, args=[runCmd,job_name,process_only],trigger=IntervalTrigger(seconds=int(job_time)),replace_existing= True)
        
        elif trig=="cron":
            
            scheduler.add_job(func= __name__ + ":runJob",id=job_name, args=[runCmd,job_name,process_only],trigger=CronTrigger(
                second = jobList['second'] ,minute=jobList['minute'] ,hour=jobList['hour'],
                              day = jobList['day'],month = jobList['month'],year = jobList['year'],
                              week = jobList['week'],day_of_week = jobList['day_of_week']
                ),replace_existing= True
            )
            
        
        else:
            return printJson("参数错误");
        
        return printJson("操作成功");
    
    else:
        return printJson("数据库写入失败");
    
    print(scheduler.get_job(job_name)); 
    
#获取任务数据
@app.route('/editJob', methods=['POST'])
def editJob():
    
    id = request.form['id'];
    job_name = request.form['job_name'];
    job_desc = request.form['job_desc'];
    cmd_type = request.form['cmd_type'];
    process_only = request.form['process_only'];
    cmd = request.form['cmd'];
    job_time = request.form['job_time'];
    update_time = int(time.time());
    
    jobList = checkJobTime(job_time);
    print(jobList);
    
    if jobList ==False:
        return printJson("执行周期格式错误");
    trig = jobList['trig'];
    
    selectSql = "select id from job_cron where job_name = '%s'  limit 1"%(job_name);
    
    
    selectRow = db_conn.execute(text(selectSql))
    selectRes = [{key: value for (key, value) in row.items()} for row in selectRow];
    
    if selectRes:
        pass;
    
    else:
        return printJson('无此数据', {}, 1);
    

    values = "`job_name` = '%s',`job_desc` ='%s',`cmd_type` = '%s',`cmd` = '%s',`trig` = '%s',`job_time` = '%s',`update_time` = '%s' "%(job_name,job_desc,cmd_type,cmd,trig,job_time,update_time);
    sql = "update job_cron set %s where id = '%s' "%(values,str(id));
    res = db_conn.execute(sql);
    
    
    if res :
        runCmd = cmd_type + " " + Config().RUNPATH  + cmd;
        if trig =="interval":
            
            scheduler.add_job(func= __name__ + ":runJob",id=job_name, args=[runCmd,job_name,process_only],trigger=IntervalTrigger(seconds=int(job_time)),replace_existing= True)
        
        elif trig=="cron":
            
            scheduler.add_job(func= __name__ + ":runJob",id=job_name, args=[runCmd,job_name,process_only],trigger=CronTrigger(
                second = jobList['second'] ,minute=jobList['minute'] ,hour=jobList['hour'],
                              day = jobList['day'],month = jobList['month'],year = jobList['year'],
                              week = jobList['week'],day_of_week = jobList['day_of_week']
                ),replace_existing= True
            )
                    
        else:
            return printJson("参数错误");
            
        return printJson("操作成功");
        
    else:
        return printJson("数据库写入失败");
        
#任务状态
@app.route('/jobStats',methods=['GET'])
def jobStats():
    
    job_name = request.args['job_name'];
    getJobUuid = scheduler.get_job(job_name);
    if getJobUuid:
    
        print(getJobUuid.next_run_time);
        next_run_time = str(getJobUuid.next_run_time);
        
        return ("任务id:" + getJobUuid.id + "<br/>下次执行时间:" +  next_run_time);
    else:
        
        return ("任务已暂停，请点恢复进行激活");
    
#任务暂停
@app.route('/pauseJob',methods=['GET'])
def pauseJob():
    
    job_name = request.args['job_name'];
    getJobUuid = scheduler.get_job(job_name);
    if getJobUuid:
        
        sql = "update job_cron set `stats`= 1 where `job_name`='%s' limit 1"%(job_name);
        res = db_conn.execute(sql);
        
        if res:
            #scheduler.pause_job(job_name);
            scheduler.remove_job(job_name);
            return printJson("任务暂停成功", {}, 0)
    
    else:
        return printJson("任务id不存在或者执行进程已退出", {}, 1)


   
#任务恢复
@app.route('/resumeJob')
def resumeJob():
    job_name = request.args['job_name'];
    
    selectSql = "select * from job_cron where job_name = '%s'  limit 1"%(job_name);
    rows = db_conn.execute(selectSql);
    rowRes = [{key: value for (key, value) in row.items()} for row in rows];
    selectRes = rowRes[0];
    
    if selectRes:
    
        runCmd = selectRes['cmd_type'] + " " +  Config().RUNPATH + selectRes['cmd'];
        process_only = selectRes['process_only'];
        trig = selectRes['trig'];
        jobList = checkJobTime(selectRes['job_time']);
        if jobList ==False:
            return printJson("执行周期格式错误");
        
        job_time = selectRes['job_time']
        
        
        sql = "update job_cron set `stats`= 0 where `job_name`='%s' limit 1"%(job_name);
        res = db_conn.execute(sql);
    
        if res:
            if trig =="interval":
                scheduler.add_job(func= __name__ + ":runJob",id=job_name, args=[runCmd,job_name,process_only],trigger=IntervalTrigger(seconds=int(job_time)),replace_existing= True)
            
            elif trig=="cron":
                scheduler.add_job(func= __name__ + ":runJob",id=job_name, args=[runCmd,job_name,process_only],trigger=CronTrigger(
                    second = jobList['second'] ,minute=jobList['minute'] ,hour=jobList['hour'],
                            day = jobList['day'],month = jobList['month'],year = jobList['year'],week = jobList['week'],day_of_week = jobList['day_of_week']
                        
                    ),replace_existing= True
                )
           
            return printJson("任务恢复成功", {}, 0)
            
        else:
            return printJson("任务id不存在或者执行进程已退出", {}, 1)    
                    
    else:
            return printJson("数据不存在", {}, 1)                      
   
   
#任务删除
@app.route('/removeJob')
def removeJob():
    
    job_name = request.args['job_name'];
    getJobUuid = scheduler.get_job(job_name);
    if getJobUuid:
        sql = "delete from  job_cron  where `job_name`='%s' limit 1"%(job_name);
        res = db_conn.execute(sql);
        
        if res:
            scheduler.remove_job(job_name);
            return printJson("任务移除成功", {}, 0)
    else:
        return printJson("任务id不存在或者执行进程已退出", {}, 1)
    
      

 
#获取任务数据
@app.route('/getJob', methods=['POST'])
def getJob():
    id = request.form['id'];
    
    sql = "select * from job_cron where id ='%s' and stats >=0 limit 1 "%(str(id));
    
    rows = db_conn.execute(text(sql))
    res = [{key: value for (key, value) in row.items()} for row in rows];
    
    if res:
        return printJson('成功', res, 0)
    else:
        return printJson('无此数据', {}, 1)
    
    
    
   
#任务查询
@app.route('/checkJob', methods=['POST'])
def checkJob():
    job_name = request.form['job_name'];
    cmd_type = request.form['cmd_type'];
    cmd = request.form['cmd'];
    
    sql = "select * from job_cron where job_name ='%s' or (`cmd_type`='%s' and  `cmd`='%s' and stats >=0) limit 1 "%(job_name,cmd_type,cmd);
    res = db_conn.execute(text(sql)).fetchall()
   
    if res:
        if res[0]['job_name'] == job_name:
            return printJson("任务名称已经存在",'', 1);
        elif(res[0]['cmd_type'] == cmd_type and res[0]['cmd'] == cmd):
            return printJson("相同命令不能重复添加",'', 1);
        else:
            return printJson("成功",'', 0);
    else:
        return printJson("成功",'', 0);
    
    

#通过job_time 判断任务时间类型
def checkJobTime(job_time):
    job = {};
    job['trig'] = 'interval';
    job['job_time'] = job_time;
    
    job_timeTem = job_time.split(' ');
    
    if len(job_timeTem)==1 and int(job_time)>0:
        return job; 
    
    if len(job_timeTem)==8:
        job['trig'] = 'cron';
        
        job_timeList = [];
        for i in job_timeTem:
            if i != "":
                job_timeList.append(i);
        
        if len(job_timeList) ==8:
            second = job_timeList[0];
            day_of_week = job_timeList[4];
            
            job['second'] = job_timeList[0];
            job['minute'] = job_timeList[1];
            job['hour'] = job_timeList[2];
            job['day']  = job_timeList[3];
            job['month']   = job_timeList[4];
            job['year']    = job_timeList[5];
            job['week']     = job_timeList[6];
            job['day_of_week']    = job_timeList[7];
            
            if second == '*':
                job['second'] = '00';
            
            if day_of_week == '*':
                job['day_of_week'] = '0-6';
                
        return job;   

    else:
        return False;


#查看log
@app.route('/logView', methods=['GET'])
def logView():
    logPath = request.args.get('logPath');
    logFilePath = Config().LOGPATH + logPath;
    if os.path.exists(logFilePath):
        
        f = open(Config().LOGPATH + logPath,'r');
        content = f.read().replace('\n','<br/>');
        f.close();
        return  content;
    
    else:
        return  "无此文件，日志文件第一次执行的时候生成";
    
           

def printJson(msg="操作成功",data=[],status=0):
    
    info = {};
    info['msg'] = msg;
    info['data'] = data;
    info['status'] = status;
    
    
    return jsonify(info);
    


if __name__ == '__main__':
    
    scheduler = BackgroundScheduler(jobstores=jobstores, executors=executors, job_defaults=job_defaults, timezone=utc)
    #scheduler.init_app(app)
    scheduler.start()
    
    app.run(debug = True)       
    
    
    